﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;

namespace FiniteStateMachine
{
    public abstract class FiniteStateMachineBase:FsmEventBase
    {
        private FsmProcessor _FsmProcessor = null;
        private FsmClassInfo _ClassInfo = null;

        public FsmProcessor FsmProcessor
        {
            get { return _FsmProcessor; }
            set { _FsmProcessor = value; }
        }

        public FiniteStateMachineBase():this(true)
        {
        }

        public FiniteStateMachineBase(bool synchronized):base(synchronized)
        {
            this._ClassInfo = (FsmClassInfo) FsmClassInfo.FsmClassInfoMap[this.GetType()];
            if(this._ClassInfo ==null)
            {
                this.InitializeFsmClassInfo();
            }
            Debug.Assert(this._ClassInfo !=null);
        }

        public bool HaveStates
        {
            get
            {
                return this._ClassInfo.HaveStates;
            }
        }

        public void PushEvent(FsmEvent evt)
        {
            this._FsmProcessor.PushEvent(evt,this);
        }

        public void PushEvent(FsmTimerEvent evt)
        {
            this._FsmProcessor.PushEvent(evt,this);
        }

        public void Terminate()
        {
            this._FsmProcessor.TerminateFsm(this);
        }

        protected internal virtual void OnFsmEntry()
        {
            // do nothing, override this method if needed in derived class
        }

        protected internal virtual void OnFsmExit()
        {
            // do nothing, override this method if needed in derived class
        }

        protected internal virtual void EnterFirstState()
        {
            Debug.Assert(this._ClassInfo != null);
            if (this._ClassInfo.HaveStates)
            {
                Type typeFsm = this.GetType();
                Int32 currState = this.GetCurrentState();
                StateInfo stateInfo = this._ClassInfo.StateInfoArray[currState];
                // execute entry state handler of current first state
                if (stateInfo.EntryMethod != null)
                {
                    stateInfo.EntryMethod.Invoke(this,
                                                 new object[]
                                                     {null, Enum.ToObject(this._ClassInfo.StateEnumType, currState)});
                    Debug.Assert(currState == this.GetCurrentState());
                }
            }
        }

        /// <summary>
        /// this is the core event dispatching of the FSM. 
        /// The FSM looks up the current state and calls the state and transition handlers  
        /// if they are defined
        /// 
        /// Override if possible but not recommended
        /// </summary>
        /// <param name="evt"></param>
        protected internal virtual void OnFsmEvent(FsmEvent evt)
        {
            Debug.Assert(this._ClassInfo !=null);
            if(this._ClassInfo.HaveStates)
            {
                Int32 currState = this.GetCurrentState();
                // execute transition
                StateInfo stateInfo1 = this._ClassInfo.StateInfoArray[currState];
                Debug.Assert(stateInfo1 !=null);
                MethodInfo methInfoTransition = (MethodInfo) stateInfo1.Transitions[evt.GetType()];
                if (methInfoTransition != null)
                {
                    // first execute exit method
                    if(stateInfo1.ExistMethod !=null)
                    {
                        stateInfo1.ExistMethod.Invoke(this, new object[] {evt});
                        Debug.Assert(currState==this.GetCurrentState());
                    }

                    // now execute transition method
                    methInfoTransition.Invoke(this, new object[] {evt});
                    
                    // on return, FSM has probably a new state
                    int newState = this.GetCurrentState();
                    if(newState!=currState)
                    {
                        StateInfo stateInfo2 = this._ClassInfo.StateInfoArray[newState];
                        Debug.Assert(newState == this.GetCurrentState());

                        // execute entry state handler of new state
                        if(stateInfo2.EntryMethod !=null)
                        {
                            stateInfo2.EntryMethod.Invoke(this,
                                                          new object[]
                                                              {
                                                                  evt,
                                                                  Enum.ToObject(this._ClassInfo.StateEnumType, currState)
                                                              });
                            // it is not allowed to change state in StateEntryHandler
                            Debug.Assert(newState==this.GetCurrentState());
                        }
                    }
                    else
                    {
                        // it is the same state => transition loop
                        if(stateInfo1.EntryMethod !=null)
                        {
                            stateInfo1.EntryMethod.Invoke(this,
                                                          new object[]
                                                              {
                                                                  evt,
                                                                  Enum.ToObject(this._ClassInfo.StateEnumType, currState)
                                                              });
                            Debug.Assert(currState==this.GetCurrentState());
                        }
                    }
                }
                else
                {
                    // there is no transition for this event in current state
                    if(stateInfo1.DefaultTransitionMethod !=null)
                    {
                        stateInfo1.DefaultTransitionMethod.Invoke(this, new object[] {evt});
                    }
                    else
                    {
                        MethodInfo metInfoEvtHnd = (MethodInfo) this._ClassInfo.EventHandlers[evt.GetType()];
                        if(metInfoEvtHnd !=null)
                        {
                            metInfoEvtHnd.Invoke(this, new object[] {evt});
                        }
                        else
                        {
                            this.OnFsmEventDefault(evt);
                        }
                    }
                }
            }
            else
            {
                // FSM has no state, therefore, event handlers are the only handlers to check
                MethodInfo methodInfo = (MethodInfo) this._ClassInfo.EventHandlers[evt.GetType()];
                if(methodInfo !=null)
                {
                    methodInfo.Invoke(this, new object[] {evt});
                }
                else
                {
                    // Error: no matching event handler available
                    Trace.WriteLine("FSM: " + this.GetType().Name + " event: " + evt.GetType().Name +
                                    " no event handler found.");

                }
            }
        }

        protected internal virtual void OnFsmEventDefault(FsmEvent evt)
        {
            if(this._ClassInfo.HaveStates)
            {
                Trace.WriteLine("FSM: "+this.GetType().Name +" Event:"+evt.GetType().Name +
                    " no handler found."+Enum.GetName(this._ClassInfo.StateEnumType,this.GetCurrentState()));
            }
            else
            {
                Trace.WriteLine("FSM:"+this.GetType().Name +" Event"+evt.GetType().Name +
                    " not handled");
            }
        }

        protected void TerminateFsm()
        {
            Debug.Assert(Thread.CurrentThread.GetHashCode()==this._FsmProcessor.Thread.GetHashCode());
            this.OnFsmExit();
            this.Done();
        }

        public virtual void FsmDone()
        {
            if(Thread.CurrentThread.GetHashCode()==this._FsmProcessor.Thread.GetHashCode())
            {
                this.TerminateFsm();
            }
            else
            {
                this._FsmProcessor.TerminateFsm(this);
            }
        }

        /// <summary>
        /// unfortunately, this method must be overriden, if states are used in this FSM
        /// It is needed since this FSM base class needs knowledge about the state defined in the 
        /// derived class.
        /// </summary>
        /// <returns></returns>
        protected internal virtual int GetCurrentState()
        {
            Debug.Assert(false);
            Trace.WriteLine("GetCurrentState method must be overriden when defining a state variable");
            return -1;
        }

        private void InitializeFsmClassInfo()
        {
            Type typeFsm = this.GetType();
            Debug.Assert(this._ClassInfo ==null);
            this._ClassInfo=new FsmClassInfo();
            this._ClassInfo.Name = typeFsm.Name;
            FsmClassInfo.FsmClassInfoMap[typeFsm] = this._ClassInfo;

            // check, if client want to use attributes for defining state var and handler
            object[] fsmCodingAttributes = typeFsm.GetCustomAttributes(typeof (FsmCodingAttribute), true);
            Debug.Assert(fsmCodingAttributes.Length<=1);
            if(fsmCodingAttributes.Length>0)
            {
                FsmCodingAttribute attr = (FsmCodingAttribute) fsmCodingAttributes[0];
                this._ClassInfo.FsmCodingType = attr.CodingType;
            }

            // get state member variable if available
            MemberInfo[] stateFields;
            if(this._ClassInfo.FsmCodingType==ECodingType.Automatic)
            {
                stateFields =
                    typeFsm.GetMember("state", MemberTypes.Field,
                                      BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            }
            else
            {
                stateFields =
                    typeFsm.FindMembers(MemberTypes.Field,
                                        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                                        new MemberFilter(HasFieldAttributeOfType), typeof (FsmStateAttribute));
                // generate a GetCurrentState method for this FSM class
                // Note: don't know how to implement... 
            }

            // get all info from state valiable and prepare state info
            FieldInfo stateField;
            FieldInfo[] stateEnums;
            Hashtable stringToStateMap=new Hashtable();
            if(stateFields.Length>0)
            {
                this._ClassInfo.HaveStates = true;
                // Get transition handlers and state handlers
                // this FSM has a state field, get the field info of it
                // fill a string map with the enumeration value names for faster lookup
                Debug.Assert(stateFields.Length==1);
                stateField =(FieldInfo) stateFields[0];
                this._ClassInfo.StateEnumType = stateField.FieldType;
                Debug.Assert(this._ClassInfo.StateEnumType.IsSubclassOf(typeof(System.Enum)));
                stateEnums =
                    this._ClassInfo.StateEnumType.GetFields(BindingFlags.Static | BindingFlags.Public |
                                                            BindingFlags.NonPublic);
                this._ClassInfo.StateInfoArray = new StateInfo[stateEnums.Length];
                foreach(FieldInfo fieldInfo in stateEnums)
                {
                    int val = (int) fieldInfo.GetValue(null);
                    StateInfo stateInfo=new StateInfo();
                    this._ClassInfo.StateInfoArray[val] = stateInfo;
                    Debug.Assert(this._ClassInfo.StateInfoArray[val]==stateInfo);
                    stateInfo.Name = fieldInfo.Name;
                    stateInfo.Transitions=new Hashtable();
                    stringToStateMap.Add(fieldInfo.Name,stateInfo);
                }
            }

            // get all methods which are candidates for any kind of handlers
            MemberInfo[] handlers =
                typeFsm.FindMembers(MemberTypes.Method,
                                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                                    new MemberFilter(IsValidFsmHandler), null);
            // loop over all these methods
            foreach(MemberInfo mi in handlers)
            {
                MethodInfo meth = (MethodInfo) mi;
                StateInfo stateInfo = null;
                if(this._ClassInfo.FsmCodingType==ECodingType.Automatic)
                {
                    // check if it is a state or transition handler
                    bool isStateOrTransitionHandler = false;
                    int separatorPos = meth.Name.IndexOf("_");
                    if(separatorPos>0)
                    {
                        string prefix = meth.Name.Substring(0, separatorPos);
                        stateInfo = (StateInfo) stringToStateMap[prefix];
                        if(stateInfo !=null)
                        {
                            isStateOrTransitionHandler = true;
                        }
                    }
                    if(isStateOrTransitionHandler)
                    {
                        if(meth.Name.EndsWith("_EntryState"))
                        {
                            Debug.Assert(meth.GetParameters().Length==2);
                            Debug.Assert(meth.GetParameters()[0].ParameterType==typeof(FsmEvent));
                            Debug.Assert(meth.GetParameters()[1].ParameterType==this._ClassInfo.StateEnumType);
                            stateInfo.EntryMethod = meth;
                        }
                        else if(meth.Name.EndsWith("_ExitState"))
                        {
                            Debug.Assert(meth.GetParameters().Length==1);
                            Debug.Assert(meth.GetParameters()[0].ParameterType==typeof(FsmEvent));
                            stateInfo.ExistMethod = meth;
                        }
                        else if(meth.GetParameters().Length==1)
                        {
                            if(meth.GetParameters()[0].ParameterType==typeof(FsmEvent))
                            {
                                // default transition
                                stateInfo.DefaultTransitionMethod = meth;
                            }
                            else if(meth.GetParameters()[0].ParameterType.IsSubclassOf(typeof(FsmEvent)))
                            {
                                // a transition
                                Type eventParamType = meth.GetParameters()[0].ParameterType;
                                Debug.Assert(stateInfo.Transitions[eventParamType]==null);
                                stateInfo.Transitions[eventParamType] = meth;
                            }
                            else
                            {
                                // do nothing, not a FSM method
                            }
                        }
                        else
                        {
                            // do nothing, not a FSM method
                        }
                    }
                    else
                    {
                        if(meth.GetParameters().Length==1 && meth.GetParameters()[0].ParameterType.IsSubclassOf(typeof(FsmEvent)))
                        {
                            Type eventParamType = meth.GetParameters()[0].ParameterType;
                            Debug.Assert(this._ClassInfo.EventHandlers[eventParamType]==null);
                            this._ClassInfo.EventHandlers[eventParamType] = meth;
                        }
                        else
                        {
                            // do nothing, not a FSM method
                        }
                    }
                }
                else
                {
                    object[] attrs;
                    attrs = mi.GetCustomAttributes(typeof (FsmTransitionHandlerAttribute), false);
                    if (attrs.Length > 0)
                    {
                        // it is a transition handler, assign it to state info
                        FsmTransitionHandlerAttribute attr = (FsmTransitionHandlerAttribute) attrs[0];
                        stateInfo = (StateInfo) stringToStateMap[attr.FromState];
                        Debug.Assert(stateInfo != null);
                        Type eventParamType = meth.GetParameters()[0].ParameterType;
                        // is it the default transition handler?
                        if (eventParamType == typeof(FsmEvent))
                        {
                            // yes, store it
                            stateInfo.DefaultTransitionMethod = meth;
                        }
                        else
                        {
                            // a normal transition handler
                            Debug.Assert(eventParamType.IsSubclassOf(typeof(FsmEvent)));
                            Debug.Assert(stateInfo.Transitions[eventParamType] == null);
                            stateInfo.Transitions[eventParamType] = meth;
                        }
                    }
                    else
                    {
                        attrs = mi.GetCustomAttributes(typeof (FsmStateHandlerAttribute), false);
                        if (attrs.Length > 0)
                        {
                            FsmStateHandlerAttribute attr = (FsmStateHandlerAttribute) attrs[0];
                            stateInfo = (StateInfo) stringToStateMap[attr.State];
                            Debug.Assert(stateInfo != null);
                            if (attr.HandlerType == EStateHandlerType.Entry)
                            {
                                Debug.Assert(meth.GetParameters().Length == 2);
                                Debug.Assert(meth.GetParameters()[0].ParameterType == typeof (FsmEvent));
                                Debug.Assert(meth.GetParameters()[1].ParameterType == this._ClassInfo.StateEnumType);
                                Debug.Assert(stateInfo.EntryMethod == null);
                                stateInfo.EntryMethod = meth;
                            }
                            else if (attr.HandlerType == EStateHandlerType.Exit)
                            {
                                Debug.Assert(meth.GetParameters().Length == 1);
                                Debug.Assert(meth.GetParameters()[0].ParameterType == typeof (FsmEvent));
                                Debug.Assert(stateInfo.ExistMethod == null);
                                stateInfo.ExistMethod = meth;
                            }
                            else
                            {
                                Trace.WriteLine("Unexpected state handler attribute value");
                                Debug.Assert(false);
                            }
                        }
                        else
                        {
                            // it is neither a transition nor a state handler
                            // it is a event handler
                            attrs = mi.GetCustomAttributes(typeof (FsmEventHandlerAttribute), false);
                            if(attrs.Length>0)
                            {
                                FsmEventHandlerAttribute attr = (FsmEventHandlerAttribute) attrs[0];
                                Debug.Assert(attr !=null);
                                Type eventParamType = meth.GetParameters()[0].ParameterType;
                                Debug.Assert(this._ClassInfo.EventHandlers[eventParamType]==null);
                                Debug.Assert(eventParamType.IsSubclassOf(typeof(FsmEvent)));
                                this._ClassInfo.EventHandlers[eventParamType] = meth;
                            }
                        }
                    }
                }
            }
        }

        private static bool HasFieldAttributeOfType(MemberInfo memberInfo, object filterCriteria)
        {
            return memberInfo.GetCustomAttributes((Type) filterCriteria, false).Length > 0;
        }

        private static bool IsValidFsmHandler(MemberInfo memberInfo, object filterCriteria)
        {
            Debug.Assert(memberInfo.MemberType==MemberTypes.Method);
            MethodInfo methodInfo = (MethodInfo) memberInfo;
            if(methodInfo.IsStatic)
                return false;
            if(methodInfo.GetCustomAttributes(typeof(FsmNoHandlerAttribute),false).Length>0)
                return false;

            // check parameters
            if(methodInfo.ReturnType!=typeof(void))
                return false;
            ParameterInfo[] paramInfos = methodInfo.GetParameters();
            if(paramInfos.Length >2)
                return false;
            if(methodInfo.DeclaringType==typeof(FiniteStateMachineBase))
                return false;
            
            return true;
        }
    }
}
